package edu.umd.fosite.graphics;

import javax.media.opengl.GL2;

import edu.umd.fosite.data.FiniteLatticeDouble2D;
import edu.umd.fosite.data.LatticeDouble2D;
import edu.umd.fosite.geom.TriangleMeshSurface;
import edu.umd.fosite.geom.Vec;

public class GLTriangleMeshSurfaceRenderer implements
		Renderer<GL2, TriangleMeshSurface> {

	@Override
	public Drawer<GL2> render(GL2 gl2, TriangleMeshSurface s) {
		final LatticeDouble2D l = s.data;
		final GL2 gl = gl2;

		final int list = gl.glGenLists(1);
		gl.glNewList(list, GL2.GL_COMPILE);
		FiniteLatticeDouble2D fl = (FiniteLatticeDouble2D) l;
		for (int x = 0; x < fl.getXDim() - 1; x++) {
			for (int y = 0; y < fl.getYDim() - 1; y++) {
				gl.glBegin(GL2.GL_TRIANGLES);
				glNormalAt(gl, l, x, y);
				gl.glVertex3d(x, fl.get(x, y), y);
				glNormalAt(gl, l, x + 1, y);
				gl.glVertex3d(x + 1, fl.get(x + 1, y), y);
				glNormalAt(gl, l, x, y + 1);
				gl.glVertex3d(x, fl.get(x, y + 1), y + 1);

				glNormalAt(gl, l, x + 1, y + 1);
				gl.glVertex3d(x + 1, fl.get(x + 1, y + 1), y + 1);
				glNormalAt(gl, l, x + 1, y);
				gl.glVertex3d(x + 1, fl.get(x + 1, y), y);
				glNormalAt(gl, l, x, y + 1);
				gl.glVertex3d(x, fl.get(x, y + 1), y + 1);
				gl.glEnd();
			}
		}
		gl.glEndList();

		return new RawDrawer<>(new Runnable() {

			@Override
			public void run() {
				// TODO generalize to infinite lattices
				gl.glCallList(list);
			}

		});
	}

	public Vec[] vectorsAround(LatticeDouble2D l, int i, int j) {
		Vec[] r = new Vec[4];
		r[0] = new Vec(-1, 0, l.get(i, j) - l.get(i + 1, j));
		r[1] = new Vec(0, -1, l.get(i, j) - l.get(i, j + 1));
		r[2] = new Vec(1, 0, l.get(i, j) - l.get(i - 1, j));
		r[3] = new Vec(0, 1, l.get(i, j) - l.get(i, j - 1));
		return r;
	}

	public Vec[] normalsAround(LatticeDouble2D l, int i, int j) {
		Vec[] a = vectorsAround(l, i, j);
		Vec[] r = new Vec[4];
		for (int c = 0; c < 4; c++)
			r[c] = a[c].clone().cross(a[(c + 1) % 4]);
		return r;
	}

	public Vec normalAt(LatticeDouble2D l, int i, int j) {
		Vec[] a = normalsAround(l, i, j);
		return a[0].add(a[1]).add(a[2]).add(a[3]).scale(0.25);
	}

	public void glNormalAt(GL2 gl, LatticeDouble2D l, int i, int j) {
		Vec norm = normalAt(l, i, j);
		gl.glNormal3d(norm.X, norm.Z, norm.Y);
	}

}
